package com.automapper.query;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.Table;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.automapper.constants.FetchMode;
import com.automapper.criterion.Criterion;
import com.automapper.criterion.ExpressionContext;
import com.automapper.criterion.PageExpression;
import com.automapper.criterion.Restrictions;
import com.automapper.criterion.SqlAssemble;
import com.automapper.exception.AutoMapperException;
import com.automapper.sqlfragment.SqlFragmentAssembler;
import com.automapper.sqlfragment.SqlFragmentContext;
import com.automapper.utils.FieldFacade;

public class CriteriaImpl implements Criteria {
	private static final Logger logger = LoggerFactory.getLogger(CriteriaImpl.class);
	private ExpressionContext expressionContext = new ExpressionContext();	
	private SqlAssemble sqlAssemble = new SqlAssemble();
	private SqlFragmentContext sqlFragmentContext = new SqlFragmentContext();
	private Map<String, SqlFragmentAssembler> sqlFragmentAssemblerMap = new HashMap<>();
	private FetchMode fetchMode = FetchMode.DEFAULT;

	public CriteriaImpl(Class<?> clazz) {
		this(clazz, null, null, FetchMode.DEFAULT);
	}
	
	public CriteriaImpl(Class<?> clazz, String tableName, String alias, FetchMode fetchMode) {
		try {
			tableName = initTableName(clazz, tableName);
			alias = initAlias(clazz, alias);
			SqlFragmentAssembler sqlFragmentAssembler = new SqlFragmentAssembler(clazz, tableName, alias, fetchMode, expressionContext);
			sqlFragmentAssemblerMap.put(alias, sqlFragmentAssembler);
		} catch (Exception e) {
			logger.error("", e);
			throw new RuntimeException(e);
		}
	}

	@Override
	public Criteria add(Criterion expression) {
		expressionContext.addExpression(expression);
		return this;
	}

	public ExpressionContext getExpressionContext() {
		return expressionContext;
	}

	public SqlAssemble getSqlAssemble() {
		return sqlAssemble;
	}

	@Override
	public Criteria setMaxResults(int maxResults) {
		PageExpression pageExpression = new PageExpression();
		pageExpression.setRows(maxResults);
		expressionContext.setPageExpression(pageExpression);
		return this;
	}
	
	private SqlFragmentAssembler getCascadeAssembler(SqlFragmentAssembler sqlFragmentAssembler, Map<String, SqlFragmentAssembler> sqlFragmentAssemblerMap) {
		Class<?> clazz = sqlFragmentAssembler.getOriginalClass();
		for (Entry<String, SqlFragmentAssembler> entry : sqlFragmentAssemblerMap.entrySet()) {
			SqlFragmentAssembler assembler = entry.getValue();
			Class<?> cascadeClazz = assembler.getNestedResultsClassPath();
			if (cascadeClazz == null) {
				continue;
			}
			
			if (StringUtils.equalsIgnoreCase(clazz.getName(), cascadeClazz.getName())) {
				return assembler;
			}
		}
		return null;
	}
	
	public FetchMode getFetchMode() {
		return fetchMode;
	}

	//初始化别名，当别名是空值或空字符串，别名采用 "类名" + "_"
	private String initAlias(Class<?> clazz, String alias) {
		if (StringUtils.isBlank(alias)) {
			return StringUtils.substringAfterLast(clazz.getName(), ".") + "_";
		} else {
			return alias;
		}
	}
	
	private String initTableName(Class<?> clazz, String tableName) {
		if (StringUtils.isBlank(tableName)) {
			Table table = clazz.getAnnotation(Table.class);
			AutoMapperException.NullRuntimeException(table, "对象%s没有@Table注解", clazz);
			tableName = table.name();
		} 
		AutoMapperException.StringisBlankRuntimeException(tableName, clazz + "对象表名不能为空或空字符串");
		return tableName;
	}

	public SqlFragmentContext getSqlFragmentContext() {
		sqlFragmentContext.setSqlFragmentAssemblers(new ArrayList<>(sqlFragmentAssemblerMap.values()));
		sqlFragmentContext.setExpressionContext(expressionContext);
		return sqlFragmentContext;
	}

	@Override
	public Criteria cascade(Class<?> clazz, String tableName, String alias) {
		try {
			SqlFragmentAssembler sqlFragmentAssembler = new SqlFragmentAssembler(clazz, tableName, alias, FetchMode.DEFAULT, expressionContext);
			sqlFragmentAssemblerMap.put(alias, sqlFragmentAssembler);
			
			SqlFragmentAssembler driverAssembler =  getCascadeAssembler(sqlFragmentAssembler, sqlFragmentAssemblerMap);
			AutoMapperException.NullRuntimeException(driverAssembler, "{}找不到关联字段", clazz.getName());
			driverAssembler.setCascadeSqlFragmentAssembler(sqlFragmentAssembler);
			FieldFacade fieldFacade = sqlFragmentAssembler.getJoinColumnField();
			String propertyName = driverAssembler.getAlias() + "." + fieldFacade.getJoinColumnName();
			String otherPropertyName = sqlFragmentAssembler.getAlias() + "." + fieldFacade.getColumnName();
			//设置两表关联的字段
			add(Restrictions.eqProperty(propertyName, otherPropertyName));
		} catch (Exception e) {
			logger.error("", e);
			throw new RuntimeException(e);
		}
		return this;
	}
}
